#pragma once

#include <cstdlib>
#include <mutex>
#include <string>
#include <mysql/mysql.h>

#include "util.hpp"
#include "Log.hpp"

// 主要是针对数据库的操作
namespace aod
{
static const char* HOST = "localhost";
static const char* USER = "chen";
static const char* PASS = "123";
static const char* DBNAME = "aod_system";

    // 封装一下对数据库的操作
    class MySQL
    {
    private:
        MYSQL* _mysql = nullptr;
    public:
        bool MysqlInit()
        {
            _mysql = mysql_init(nullptr);
            if(_mysql == nullptr)
            {
                LOG(WARING, "Init mysql instance failed\n");
                return true;
            }
            // 连接数据库
            if(mysql_real_connect(_mysql, HOST, USER, PASS, DBNAME, 0, nullptr, 0) == nullptr)
            {
                // 连接失败
                LOG(WARING, "Connect mysql server failed\n");
                // 关闭连接
                mysql_close(_mysql);
            }
            mysql_set_character_set(_mysql, "utf8");
            return true;
        }

        MYSQL* Getmysql()
        {
            return _mysql;
        }

        ~MySQL()
        {
            if(_mysql != nullptr)
            {
                mysql_close(_mysql);
            }
        }

        bool MysqlQuery(const std::string& sql)
        {
            int ret = mysql_query(_mysql, sql.c_str());
            if(ret != 0)
            {
                LOG(INFO, "mysql_query failed: %s\n", sql.c_str());
                LOG(INFO, "%s\n", mysql_error(_mysql));
                return false;
            }
            return true;
        }
    };

    // 封装对表的增删查改等具体操作
    class TableVideo
    {
    private:
        MySQL _mysql;
        std::mutex _mutex;  // 在对表进行操作时进行保护
    public:
        TableVideo()
        {
            if(_mysql.MysqlInit() == false)
            {
                exit(-1);
            }
        }

        // Json串形式的参数，后面要反序列化
        bool Insert(const Json::Value& video)
        {
            // 表的格式：id(主键) name(视频名称) info(视频简介) video(视频文件链接) image(视频封面图片链接)
            std::string sql;
            sql.resize(4096 + video["info"].asString().size()); // 提前扩容，并且防止简介过长
            #define INSERT_VIDEO "insert tb_video values(null, '%s', '%s', '%s', '%s');"
            if(video["name"].asString().size() == 0)
            {   
                // 插入数据不合规,这里只对name列做了粗略的校验，实际上要进行更多且更严格的校验
                return false;
            }
            sprintf(&sql[0], INSERT_VIDEO, video["name"].asCString(),
						video["info"].asCString(), 
						video["video"].asCString(),
						video["image"].asCString());

            return _mysql.MysqlQuery(sql);
        }

        // 更新数据，需要对应的索引
        bool Update(const int& video_id, const Json::Value& video)
        {
            std::string sql;
			sql.resize(4096 + video["info"].asString().size());//防止简介过长
			#define UPDATE_VIDEO "update tb_video set name='%s', info='%s' where id=%d;"
            sprintf(&sql[0], UPDATE_VIDEO, video["name"].asCString(),
                    video["info"].asCString(), video_id);
            
            return _mysql.MysqlQuery(sql);
        }

        // 删除表项，只需要索引即可
        bool Delete(const int& video_id)
        {
            #define DELETE_VIDEO "delete from tb_video where id=%d;"
            std::string sql;
            sql.resize(1024);
            sprintf(&sql[0], DELETE_VIDEO, video_id);

            return _mysql.MysqlQuery(sql);
        }

        // 查找所有表项，并将数据传递出去
        bool SelectAll(Json::Value& videos)
        {
            #define SELECTALL_VIDEO "select * from tb_video;"
            _mutex.lock();
            int ret = _mysql.MysqlQuery(SELECTALL_VIDEO);
            if(ret != 0)
            {
                _mutex.unlock();
                return false;
            }

            MYSQL_RES* res = mysql_store_result(_mysql.Getmysql());
            if(res == nullptr)
            {
                LOG(WARING, "mysql store result faield\n");
                _mutex.unlock();
                return false;
            }
            _mutex.unlock();

            return true;
        }

        // 查找一个表项，并将数据传递出去
        bool SelectOne(int video_id, Json::Value& video)
        {
            #define SELECTONE_VIDEO "select * from tb_video where id=%d;"
            char sql[1024];
            snprintf(sql, sizeof(sql), SELECTONE_VIDEO, video_id);
            _mutex.lock();
            bool ret = _mysql.MysqlQuery(sql);
            if(ret == false)
            {
                _mutex.unlock();
                return false;
            }
            MYSQL_RES * res = mysql_store_result(_mysql.Getmysql());
            if(res == nullptr)
            {
                LOG(WARING, "mysql store result failed\n");
                _mutex.unlock();
                return false;
            }
            _mutex.unlock();
            int num_rows = mysql_num_rows(res);
            if(num_rows != 1)
            {
                LOG(INFO, "Have no data! \n");
                mysql_free_result(res);
                return false;
            }
            MYSQL_ROW row = mysql_fetch_row(res);
            video["id"] = video_id;
            video["name"] = row[1];
            video["info"] = row[2];
            video["video"] = row[3];
            video["image"] = row[4];
            mysql_free_result(res);
            return true;
        }

        // 模糊查找
        bool SelectLike(const std::string& key, Json::Value& videos)
        {
            #define SELECTLIKE_VIEDO "select * from tb_video where name like '%%%s%%';"
            char sql[1024];
            snprintf(sql, sizeof(sql), SELECTLIKE_VIEDO, key.c_str());
            _mutex.lock();
            bool ret = _mysql.MysqlQuery(sql);
            if(ret == false)
            {
                _mutex.unlock();
                return false;
            }
            MYSQL_RES * res = mysql_store_result(_mysql.Getmysql());
            if(res == nullptr)
            {
                LOG(WARING, "mysql store result failed\n");
                _mutex.unlock();
                return false;
            }
            _mutex.unlock();
            int num_rows = mysql_num_rows(res);
            for(int i = 0; i < num_rows; i++)
            {
                MYSQL_ROW row = mysql_fetch_row(res);
                Json::Value video;
                video["id"] = atoi(row[0]);
                video["name"] = row[1];
                video["info"] = row[2];
                video["video"] = row[3];
                video["image"] = row[4];
                videos.append(video);
            }
            mysql_free_result(res);
            return true;
        }
    };
}
